package com.bolt.admin.module.sys.service;

import com.bolt.admin.module.sys.entity.MenuEntity;
import com.bolt.common.io.DirectoryUtil;
import com.bolt.common.io.FastByteArrayOutputStream;
import com.bolt.common.io.FileUtil;
import com.bolt.common.io.IoUtil;
import com.bolt.common.io.resource.ClassPathResource;
import com.bolt.common.utils.StrUtil;
import com.bolt.convention.exception.ServiceException;
import com.bolt.core.view.components.FormItem;
import com.bolt.core.view.components.ListDataLoad;
import com.bolt.core.view.components.ValidatorRule;
import com.bolt.support.codegenerator.db.AbstractTableFactory;
import com.bolt.support.codegenerator.db.TableFactory;
import com.bolt.support.codegenerator.model.Table;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.support.ServletContextResourcePatternResolver;

import javax.servlet.ServletContext;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.*;

/**
 * @author michao
 *         Created by michao on 2018/2/7.
 */
@Component
public class CodeGeneratorService implements ListDataLoad, InitializingBean, ServletContextAware, ResourceLoaderAware {

    private static Logger logger = LoggerFactory.getLogger(CodeGeneratorService.class);

    @Autowired
    protected ServletContext servletContext;

    @Autowired
    private MenuService menuService;


    public static final String CONTEXT_PATH = "base";

    public static final String ROOT_PATH = "classpath:/templates";

    public static final String RESOURCE_PATTERN = "/*.xml";

    @Value("${project.src.dir}")
    private String rootDir;

    @Value("${jdbc.jdbcUrl}")
    private String url;

    @Value("${jdbc.user}")
    private String username;

    @Value("${jdbc.pwd}")
    private String password;

    @Value("${jdbc.driverClass}")
    private String driverClass;


    private ServletContextResourcePatternResolver resourcePatternResolver;


    private TableFactory tableFactory;


    private Configuration getConfiguration() throws TemplateException {
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_25);
        // 设置FreeMarker的模版文件位置
        cfg.setClassForTemplateLoading(CodeGeneratorService.class, "/code_templates");
        cfg.setNumberFormat("###############");
        cfg.setBooleanFormat("true,false");
        cfg.setDefaultEncoding("UTF-8");
        return cfg;
    }


    /**
     * 取出数据所有表描述
     *
     * @return
     */
    public Map <String, String> getAllTables() {
        try {
            return tableFactory.getAllTables();
        } catch (Exception ex) {
            logger.error("get all table list error", ex);
            throw new ServiceException(ex);
        }
    }


    public Table getTable(String tableName) {
        try {
            return tableFactory.getTable(tableName);
        } catch (SQLException ex) {
            logger.error("find table " + tableName + "no found", ex);
            throw new ServiceException(ex);
        }
    }


    public String generatorJavaCode(Table table, String templateName) {
        try {
            Configuration cfg = getConfiguration();
            Map <String, Object> root = new HashMap <>();
            root.put("table", table);
            return process(cfg.getTemplate(templateName), root);
        } catch (Exception ex) {
            logger.error("generate error", ex);
            throw new ServiceException(ex);
        }
    }

    public void generatorCodeFile(String basePackage, Long moduleId,String rootPath, String subDir, String fileName, String code) {
        try {
            if (StrUtil.isBlank(basePackage)) {
                throw new ServiceException("请输入包名");
            }

            String packageDirName = basePackage.replace('.', '/');
            Path path = Paths.get(StrUtil.isNotBlank(rootPath)?rootPath:rootDir, packageDirName, subDir);
            Configuration cfg = getConfiguration();
            Map <String, Object> root = new HashMap <>();
            root.put("package", basePackage);
            if (moduleId != null) {
                Optional <MenuEntity> menu = menuService.findOne(moduleId);
                menu.ifPresent(one -> {
                    root.put("url", one.getUrl());
                });
            }
            StringTemplateLoader stringLoader = new StringTemplateLoader();
            stringLoader.putTemplate("code", code);
            cfg.setTemplateLoader(stringLoader);
            process(cfg.getTemplate("code", "utf-8"), root, path.toString(), fileName);
        } catch (Exception ex) {
            throw new ServiceException(ex);
        }

    }

    /**
     * 生成模块首页文件
     *
     * @param modulePath
     * @param moduleTitle
     * @param dbGridId
     */
    public void generatorIndexFile(String modulePath, String moduleTitle, String dbGridId) {

        try {

            String tempPath = StrUtil.subBefore(modulePath, "/", true);
            String moduleName = StrUtil.subAfter(tempPath, "/", true);
            String subModuleName = StrUtil.subAfter(modulePath, "/", true);

            ClassPathResource resource = new ClassPathResource(ROOT_PATH + modulePath);
            String absolutePath = resource.getAbsolutePath();
            absolutePath = StrUtil.subSuf(absolutePath,1);
            absolutePath = StrUtil.subBefore(absolutePath, "target", true);
            Path path = Paths.get(absolutePath,"/src/main/resources/templates",modulePath);

            Configuration cfg = getConfiguration();
            Map <String, Object> model = new HashMap <>();
            model.put(CONTEXT_PATH, this.servletContext.getContextPath());
            model.put("moduleTitle", moduleTitle);
            model.put("url", modulePath);
            model.put("dbGridId", dbGridId);
            model.put("moduleName", moduleName);
            model.put("subModuleName", subModuleName);
            process(cfg.getTemplate("/index.ftl"), model, path.toString(), "index.html");
            process(cfg.getTemplate("/input-index.ftl"), model,  path.toString(), "input-index.html");
        } catch (Exception ex) {
            logger.error("generate error", ex);
            throw new ServiceException(ex);
        }
    }

    /**
     * 生成表单文件
     *
     * @param modulePath 模块路径
     * @param code       代码
     */
    public void generatorFormFile(String modulePath, String code) {

        ClassPathResource resource = new ClassPathResource(ROOT_PATH + modulePath);
        String absolutePath = resource.getAbsolutePath();
        absolutePath = StrUtil.subSuf(absolutePath,1);
        absolutePath = StrUtil.subBefore(absolutePath, "target", true);
        String subModuleName = StrUtil.subAfter(modulePath, "/", true);
        Path path = Paths.get(absolutePath,"/src/main/resources/templates",modulePath, subModuleName + "_form.xml");
        FileUtil.writeString(code, path.toFile(), "UTF-8");
    }

    /**
     * 根据配置生成表单代码
     *
     * @param modulePath  模块路径
     * @param moduleTitle 模块名称
     * @param formItems   表单元素
     * @return
     */
    public String generatorForm(String modulePath, String moduleTitle, List <FormItem> formItems) {
        try {
            Configuration cfg = getConfiguration();
            Map <String, Object> model = new HashMap <>();
            String tempPath = StrUtil.subBefore(modulePath, "/", true);
            String moduleName = StrUtil.subAfter(tempPath, "/", true);
            String subModuleName = StrUtil.subAfter(modulePath, "/", true);
            // model.put(CONTEXT_PATH, this.servletContext.getContextPath());
            model.put("moduleTitle", moduleTitle);
            model.put("url", modulePath);
            model.put("moduleName", moduleName);
            model.put("subModuleName", subModuleName);
            List <ValidatorRule> rules = new ArrayList <>();
            List <FormItem> queryFormItems = new ArrayList <>();
            for (FormItem item : formItems) {
                if (StrUtil.isNotBlank(item.getValidator())) {
                    ValidatorRule rule = new ValidatorRule();
                    rule.setId(item.getId());
                    rule.setValue(item.getValidator());
                }
                if (item.isQueryField()) {
                    queryFormItems.add(item);
                }
            }
            model.put("validator", rules);
            model.put("queryFormItems", queryFormItems);
            model.put("moduleName", moduleName);
            model.put("subModuleName", subModuleName);

            model.put("formItems", formItems);

            return process(cfg.getTemplate("/form.ftl"), model);

        } catch (Exception ex) {
            logger.error("generate error", ex);
            throw new ServiceException(ex);
        }

    }

    /**
     * 读取表单配置文件
     *
     * @param modulePath
     * @return
     */
    public String readFormConfig(String modulePath) {
        FastByteArrayOutputStream code = new FastByteArrayOutputStream();
        try {
            Resource[] resources = resourcePatternResolver.getResources(ROOT_PATH + modulePath + RESOURCE_PATTERN);
            if (null != resources && resources.length > 0) {
                code = IoUtil.read(resources[0].getInputStream());
            }
        } catch (IOException ex) {
        }
        return code.toString("UTF-8");
    }


    /**
     * 根据模板文件生成代码文件
     *
     * @param template
     * @param root
     * @param rootDir
     * @param fileName
     * @throws IOException
     */
    public void process(Template template, Map <String, Object> root, String rootDir, String fileName) throws IOException {
        if ((rootDir + fileName).length() > 300) {
            throw new ServiceException("Dir path too long.");
        }
        File newsDir = new File(rootDir);
        if (!newsDir.exists()) {
            newsDir.mkdirs();
        }
        logger.info("Write to file: " + rootDir +"/"+fileName);
        Writer out = new OutputStreamWriter(
                new FileOutputStream(rootDir + File.separator + fileName), "UTF-8");
        try {
            template.process(root, out);
        } catch (Exception ex) {
            throw new ServiceException(ex);
        } finally {
            out.close();
        }
    }

    /**
     * 根据模板文件生成代码
     *
     * @param template
     * @param root
     * @return
     * @throws ServiceException
     */
    private String process(Template template, Map <String, Object> root) throws ServiceException {
        StringWriter output = new StringWriter();
        try {
            template.process(root, output);
            return output.toString();
        } catch (Exception ex) {
            throw new ServiceException(ex);
        } finally {
            try {
                output.close();
            } catch (IOException ex) {
                throw new ServiceException(ex);
            }
        }
    }


    @Override
    public Map <String, String> load(FormItem formItem, Map  map) {
        Map <String, String> tables = this.getAllTables();
        return tables;
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        tableFactory = AbstractTableFactory.getInstance(url, username, password, driverClass);
    }


    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        resourcePatternResolver = new ServletContextResourcePatternResolver(resourceLoader);
    }

    /**
     * 取包名
     *
     * @return
     */
    public Map <String, String> loopAllEntityPackage() {
        Map <String, String> packageNameSelectData = new LinkedHashMap <>();
        List <File> packages = DirectoryUtil.loopDirs(rootDir, "entity*");
        for (File dir : packages) {
            String path = dir.getPath();
            path = StrUtil.subSuf(path, path.indexOf("com"));
            String name = path.replace(File.separator, ".");
            packageNameSelectData.put(name, name);
        }
        return packageNameSelectData;
    }

    /***
     * 取类名
     * @param packageName
     * @return
     */
    public List <String> filterAllEntityName(String packageName) {
        List <String> entityName = new ArrayList <>();
        String packageDirName = packageName.replace('.', '/');
        Path path = Paths.get(rootDir, packageDirName);
        File[] javaFile = DirectoryUtil.local(path.toString(), ".*\\.java");
        for (File file : javaFile) {
            entityName.add(FileUtil.getPainFileName(file.getName(), " entity"));
        }
        return entityName;
    }

}
